﻿using System;
using System.ComponentModel;
using System.Globalization;
using System.Text;
using System.Windows;
using Notoric.Commanding.CommandBase;

namespace Notoric.ViewModel.Elements.CWNotation
{
    /// <summary>
    /// Adapts a <see cref="Model.Elements.CWNotation.DynamicMark"/> to a Windows Presentation Foundation View.
    /// </summary>
    public class DynamicMark : Attachment, IPositionable
    {
        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="DynamicMark"/> class.
        /// </summary>
        /// <param name="owner">
        /// <para>
        /// Type: <see cref="Staff"/>
        /// </para>
        /// <para>
        /// The staff that directly owns this mark.
        /// </para>
        /// </param>
        /// <param name="adaptedElement">
        /// <para>
        /// Type: <see cref="DynamicMark"/>
        /// </para>
        /// <para>
        /// The adapted element.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// Any of the arguments is <see langword="null"/>.
        /// </exception>
        public DynamicMark(Staff owner, Model.Elements.CWNotation.DynamicMark adaptedElement)
            : base(owner, adaptedElement)
        {
            SetText();

            adaptedElement.PresentedElement.PropertyChanged += new PropertyChangedEventHandler(PresentedElement_PropertyChanged);
        }

        #endregion

        #region ViewModelElement Members

        /// <inheritdoc/>
        public override string ElementNameKey
        {
            get { return elementNameKey; }
        }

        #endregion

        #region IPositionable Members

        /// <inheritdoc/>
        public double Left
        {
            get { return Position.X; }
            set { Position = new Point(value, Position.Y); }
        }

        /// <inheritdoc/>
        public double Top
        {
            get { return Position.Y; }
            set { Position = new Point(Position.X, value); }
        }

        #endregion

        #region Event Handlers

        /// <summary>
        /// Occurs when a property of the presented dynamic mark has changed.
        /// </summary>
        /// <param name="sender">Object that has sent the event.</param>
        /// <param name="e">Event data.</param>
        private void PresentedElement_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "MarkType" || e.PropertyName == "Degree")
                SetText();
            else if (e.PropertyName == "Subito")
            {
                SetText();
                RaisePropertyChanged("IsSubito");
            }
            else if (e.PropertyName == "Expression")
                RaisePropertyChanged("Expression");
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// Sets the text of this mark.
        /// </summary>
        private void SetText()
        {
            var adaptedMark = (Model.Elements.CWNotation.DynamicMark)AdaptedElement;
            var compositionMark = (Model.Elements.DynamicMarkElement)adaptedMark.PresentedElement;
            var markBuilder = new StringBuilder();

            if (compositionMark.Subito)
                markBuilder.Append("s");

            string markBase = "";
            switch (compositionMark.MarkType)
            {
                case Model.Elements.DynamicMarkType.Forte:
                    markBase = "f";
                    break;
                case Model.Elements.DynamicMarkType.FortePiano:
                    markBase = "fp";
                    break;
                case Model.Elements.DynamicMarkType.Forzando:
                    markBase = "fz";
                    break;
                case Model.Elements.DynamicMarkType.MezzoForte:
                    markBase = "mf";
                    break;
                case Model.Elements.DynamicMarkType.MezzoPiano:
                    markBase = "mp";
                    break;
                case Model.Elements.DynamicMarkType.Piano:
                    markBase = "p";
                    break;
                case Model.Elements.DynamicMarkType.PianoForte:
                    markBase = "pf";
                    break;
                case Model.Elements.DynamicMarkType.Rinforte:
                    markBase = "rf";
                    break;
                case Model.Elements.DynamicMarkType.Rinforzando:
                    markBase = "rfz";
                    break;
            }

            for (int i = 0; i < compositionMark.Degree; ++i)
                markBuilder.Append(markBase);

            text = markBuilder.ToString();
            RaisePropertyChanged("Text");
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Creates an undoable command for setting the <see cref="Model.Elements.DynamicMarkElement.Degree"/> property.
        /// </summary>
        /// <param name="degree">
        /// <para>
        /// Type: <see cref="Byte"/>
        /// </para>
        /// <para>
        /// The degree.
        /// </para>
        /// </param>
        /// <returns>
        /// <para>
        /// Type: <see cref="UndoableCommandBase"/>
        /// </para>
        /// <para>
        /// The created command ready for use.
        /// </para>
        /// </returns>
        public UndoableCommandBase CreateSetDegreeCommand(byte degree)
        {
            return new SetDynamicMarkPropertyCommand<byte>(UndoStack, RedoStack)
            {
                Property = "Degree",
                Target = this,
                Value = degree
            };
        }

        /// <summary>
        /// Creates an undoable command for setting the <see cref="Expression"/>.
        /// </summary>
        /// <param name="expression">
        /// <para>
        /// Type: <see cref="String"/>
        /// </para>
        /// <para>
        /// The expression.
        /// </para>
        /// </param>
        /// <returns>
        /// <para>
        /// Type: <see cref="UndoableCommandBase"/>
        /// </para>
        /// <para>
        /// The created command ready for execution.
        /// </para>
        /// </returns>
        public UndoableCommandBase CreateSetExpressionCommand(string expression)
        {
            return new SetDynamicMarkPropertyCommand<string>(UndoStack, RedoStack)
            {
                Property = "Expression",
                Target = this,
                Value = expression
            };
        }

        /// <summary>
        /// Creates an undoable command for setting the <see cref="Model.Elements.DynamicMarkElement.Subito"/> property.
        /// </summary>
        /// <param name="isSubito">
        /// <para>
        /// Type: <see cref="bool"/>
        /// </para>
        /// <para>
        /// The new value of the property.
        /// </para>
        /// </param>
        /// <returns>
        /// <para>
        /// Type: <see cref="UndoableCommandBase"/>
        /// </para>
        /// <para>
        /// The created command ready for execution.
        /// </para>
        /// </returns>
        public UndoableCommandBase CreateSetIsSubitoCommand(bool isSubito)
        {
            return new SetDynamicMarkPropertyCommand<bool>(UndoStack, RedoStack)
            {
                Property = "Subito",
                Target = this,
                Value = isSubito
            };
        }

        /// <summary>
        /// Creates an undoable command for setting the type of the dynamic mark.
        /// </summary>
        /// <param name="type">
        /// <para>
        /// Type: <see cref="DynamicMarkType"/>
        /// </para>
        /// <para>
        /// The type.
        /// </para>
        /// </param>
        /// <returns>
        /// <para>
        /// Type: <see cref="UndoableCommandBase"/>
        /// </para>
        /// <para>
        /// The created command ready for execution.
        /// </para>
        /// </returns>
        public UndoableCommandBase CreateSetMarkTypeCommand(DynamicMarkType type)
        {
            return new SetDynamicMarkPropertyCommand<Model.Elements.DynamicMarkType>(UndoStack, RedoStack)
            {
                Property = "MarkType",
                Target = this,
                Value = (Model.Elements.DynamicMarkType)Enum.Parse(typeof(Model.Elements.DynamicMarkType), type.ToString())
            };
        }

        /// <summary>
        /// Creates an undoable command for setting the position of this mark.
        /// </summary>
        /// <param name="position">
        /// <para>
        /// Type: <see cref="Point"/>
        /// </para>
        /// <para>
        /// The new position.
        /// </para>
        /// </param>
        /// <returns>
        /// <para>
        /// Type: <see cref="UndoableCommandBase"/>
        /// </para>
        /// <para>
        /// The created command ready for use.
        /// </para>
        /// </returns>
        public UndoableCommandBase CreateSetPositionCommand(Point position)
        {
            var modelPosition = (Model.Elements.Point)StructuresConverter.ConvertBack(position, typeof(Model.Elements.Point), null, CultureInfo.CurrentCulture);

            return new SetterCommand<Model.Elements.Point>(this, "Position", modelPosition, UndoStack, RedoStack)
            {
                Name = "Set Position"
            };
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets the additional text expression of this mark.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="String"/>
        /// </para>
        /// <para>
        /// The additional text expression of this mark.
        /// It is written in front of the mark <see cref="Text"/>.
        /// </para>
        /// </value>
        public string Expression
        {
            get 
            {
                if (UnsavedChanges.ContainsKey("Expression"))
                {
                    var cmd = (SetDynamicMarkPropertyCommand<string>)UnsavedChanges["Expression"];
                    return cmd.Value;
                }
                else
                    return ((Model.Elements.DynamicMarkElement)((Model.Elements.CWNotation.DynamicMark)AdaptedElement).PresentedElement).Expression;
            }
            set
            {
                if (UnsavedChanges.ContainsKey("Expression"))
                {
                    var cmd = (SetDynamicMarkPropertyCommand<string>)UnsavedChanges["Expression"];
                    cmd.Value = value;
                }
                else
                    UnsavedChanges["Expression"] = CreateSetExpressionCommand(value);

                RaisePropertyChanged("Expression");
            }
        }

        /// <summary>
        /// Gets the value indicating whether this mark indicates a subito (sudden) change.
        /// </summary>
        /// <remarks>
        /// The purpose of this property is to simplify the data binding of a supposed
        /// checkable menu item. The subito sign is included in the <see cref="Text"/> property
        /// if this value is <see langword="true"/>.
        /// </remarks>
        /// <value>
        /// <para>
        /// Type: <see cref="Boolean"/>
        /// </para>
        /// <para>
        /// Indicates whether this mark indicates a subito (sudden) change.
        /// </para>
        /// </value>
        public bool IsSubito
        {
            get { return ((Model.Elements.DynamicMarkElement)((Model.Elements.CWNotation.DynamicMark)AdaptedElement).PresentedElement).Subito; }
        }

        /// <summary>
        /// Gets or sets the position of the mark.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Point"/>
        /// </para>
        /// <para>
        /// The position of the mark.<br/>
        /// The coordinates are given ind device independent pixels and are relative
        /// to the top-left corner of the owner section.
        /// </para>
        /// </value>
        public Point Position
        {
            get { return (Point)StructuresConverter.Convert(GetPropertyValue<Model.Elements.Point>("Position"), typeof(Point), null, CultureInfo.CurrentCulture); }
            set
            {
                if (UnsavedChanges.ContainsKey("Position"))
                {
                    var cmd = (SetterCommand<Model.Elements.Point>)UnsavedChanges["Position"];
                    cmd.Value = (Model.Elements.Point)StructuresConverter.ConvertBack(value, typeof(Model.Elements.Point), null, CultureInfo.CurrentCulture);
                }
                else
                    UnsavedChanges["Position"] = CreateSetPositionCommand(value);

                RaisePropertyChanged("Position");
            }
        }

        /// <summary>
        /// Gets the text that represents this dynamic mark.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="string"/>
        /// </para>
        /// <para>
        /// The text that corresponds to this mark.
        /// </para>
        /// </value>
        public string Text
        {
            get { return text; }
        }

        #endregion

        #region Private Fields

        /// <summary>
        /// Holds the dictionary key of a user-friendly name of this element.
        /// </summary>
        private const string elementNameKey = "ViewModel.ElementNames.DynamicMark";
        /// <summary>
        /// Holds the value of the <see cref="Text"/> property.
        /// </summary>
        private string text;

        #endregion
    }

    /// <summary>
    /// Determines the type of a <see cref="DynamicMark"/>.
    /// </summary>
    public enum DynamicMarkType
    {
        /// <summary>
        /// Piano (soft).
        /// </summary>
        Piano,
        /// <summary>
        /// Mezzo-piano (moderately soft).
        /// </summary>
        MezzoPiano,
        /// <summary>
        /// Mezzo-forte (moderately loud).
        /// </summary>
        MezzoForte,
        /// <summary>
        /// Forte (loud).
        /// </summary>
        Forte,
        /// <summary>
        /// Forzando (forcefull note).
        /// </summary>
        Forzando,
        /// <summary>
        /// Piano-Forte (first soft than loud).
        /// </summary>
        PianoForte,
        /// <summary>
        /// Forte-Piano (first loud than soft).
        /// </summary>
        FortePiano,
        /// <summary>
        /// Rinforzando (reinforced).
        /// </summary>
        Rinforzando,
        /// <summary>
        /// Rinforte.
        /// </summary>
        Rinforte
    }
}
